'''
             '####::'######:::'######::'########:'########::::'###::::'##::::'##:::::::'######::'##::: ##:
             . ##::'##... ##:'##... ##:... ##..:: ##.....::::'## ##::: ###::'###::::::'##... ##: ###:: ##:
             : ##:: ##:::..:: ##:::..::::: ##:::: ##::::::::'##:. ##:: ####'####:::::: ##:::..:: ####: ##:
             : ##:: ##:::::::. ######::::: ##:::: ######:::'##:::. ##: ## ### ##:::::: ##::::::: ## ## ##:
             : ##:: ##::::::::..... ##:::: ##:::: ##...:::: #########: ##. #: ##:::::: ##::::::: ##. ####:
             : ##:: ##::: ##:'##::: ##:::: ##:::: ##::::::: ##.... ##: ##:.:: ##:'###: ##::: ##: ##:. ###:
             '####:. ######::. ######::::: ##:::: ########: ##:::: ##: ##:::: ##: ###:. ######:: ##::. ##:
             ....:::......::::......::::::..:::::........::..:::::..::..:::::..::...:::......:::..::::..:: 

# 设计：工控万金油
# 版本：ver1.2
# 功能：集装箱（串口通讯搜索、MQTT客户端）
# 开始制作日期：
# 最后修改日期：2022-12-06
# 包含程序:Main13.py|Ui_box11.py
'''
import serial,time,random                               #调用端口
import modbus_tk.modbus_rtu as mb_rtu                   #调用modbus_rtu库
import paho.mqtt.client as mqtt                         #调用mqtt客户端库
import threading as thd
import Ui_box11
# from icon01 import img
import serial.tools.list_ports as port_list
# import base64,os
# 串口列表
def com_list():
    com_list=''
    list=port_list.comports()
    for i in list :
        com_name=i.description
        com_list+=com_name+'\n'
    return com_list

#modbus主功能逻辑
class fc_modbus():                                      #不知道叫什么线程好
    # def init(self):
    #     self.stop_en=0
    stop_en=0
    def stop():
        fc_modbus.stop_en=1
        time.sleep(0.2)
        com_list0=com_list()
        w_modbus.Display.insert('end', '串口列表：\n'+com_list0)    #打印内容到窗口  
        w_modbus.Display.yview_moveto(1)   
        #mb_master_rtu(port=端口,Baud=波特率,parity=校验码,addr=搜索到第n个地址,code=功能码,raddr=其实地址)
    def mb_master_rtu(port,baud,parity,addr,code,rwaddr,num):
        try:
            #modbus_rtu.RtuMaster(serial.Serial(端口号，波特率，数据位，校验码，停止位))
            commload=mb_rtu.RtuMaster(serial.Serial(port,baud,8,parity,1))
            #超时时间设定，单位秒
            commload.set_timeout(0.15*(1+(fc_modbus.num*0.05)))
            # commload.set_timeout(0.05)
            try:             
                Tempwet= commload.execute(addr,code,rwaddr,num) #commload.execute（站号，功能码，起始地址，数量）
                FDone='搜索到>地址：{}，波特率：{}，校验位：{}'.format(addr,baud,parity)
                return 1,FDone                          #通讯正常返回值1
            except:
                nowf='搜寻中……'+str(addr)
                return 0,nowf
        except:                                         #错误处理
            Err='连接出错！检查输入参数，串口是否被占用或未连接！'
            return 1,Err
    parity=['E','N','O']                                #校验位（固定）
    baud=[1200,2400,4800,9600,19200,14400,38400]                  #波特率（固定）
    port='COM1'                                         #端口号
    code0=3                                             #功能码
    rwaddr0=1                                           #读写地址
    addrh0=10                                           #设备结束地址
    addrh1=1                                            #设备起始地址
    num=1                                               #读写地址  
    FoundEn=0
    nowinfo=''                                          #信号传输的数据放这里的
    loadnum=0                                           #进度条
    total=0
    temp=['',0,0,0,0]
    temp_display=''
    def runapp(self):
        fc_modbus.stop_en=0
        fc_modbus.loadnum=0  
        for b in fc_modbus.baud:                              #遍历波特率
            for p in fc_modbus.parity:                        #遍历校验位                                         
                for addr0 in range(fc_modbus.addrh1,fc_modbus.addrh0+1):     #遍历地址  
                    if fc_modbus.stop_en ==1 :
                        return                
                    if addr0<=fc_modbus.addrh1:                                       
                        nowinfo='当前波特率：{}，校验位：{}'.format(b,p) #当前波特率和校验码
                        w_modbus.Display.insert('end', nowinfo + "\n")
                        w_modbus.Display.yview_moveto(1)          
                    fc_modbus.FoundEn,fc_modbus.nowinfo=fc_modbus.mb_master_rtu(port=fc_modbus.port,
                                                              baud=b,
                                                              parity=p,
                                                              addr=addr0,
                                                              code=fc_modbus.code0,
                                                              rwaddr=fc_modbus.rwaddr0,
                                                              num=fc_modbus.num)
                    fc_modbus.loadnum+=1
                    w_modbus.tag_load.set(fc_modbus.loadnum)
                    w_modbus.Display.insert('end', fc_modbus.nowinfo + "\n")
                    w_modbus.Display.yview_moveto(1)
                    w_modbus.tag_display.set(fc_modbus.FoundEn)
                    
                    if fc_modbus.FoundEn==1:                  #通讯正常或串口出错退出循环
                        break
                if fc_modbus.FoundEn==1:                      #通讯正常或串口出错退出循环
                    break
            if fc_modbus.FoundEn==1 or (b==38400 and p=='O'):
                if fc_modbus.FoundEn==0 and (b==38400 and p=='O'):
                    check0='未搜索到设备，请检查起始地址，结束地址、读取点位、功能码和读取数量（默认1即可）是否正确！'
                    w_modbus.Display.insert('end', check0 + "\n")
                    w_modbus.Display.yview_moveto(1)
                    fc_modbus.FoundEn=1
                    w_modbus.tag_display.set(fc_modbus.FoundEn)
                break                                   #通讯正常或串口出错退出循环 
    def run():
        try:
            var_port=w_modbus.in_port.get()
            var_addrh=w_modbus.in_addrh.get()
            var_addrl=w_modbus.in_addrl.get()
            var_code=w_modbus.in_code.get()
            var_rwaddr=w_modbus.in_rwaddr.get()
            var_num=w_modbus.in_num.get()
            tag0=['',0,0,0,0,0]
            tag0[0]=var_port                            #获取用户输入的端口号
            tag0[1]=var_addrh                           #获取用户输入的结束地址
            tag0[2]=var_code                            #获取用户输入的功能码
            tag0[3]=var_rwaddr                          #获取用户输入的读取地址
            tag0[4]=var_num                             #获取用户输入的读取数量
            tag0[5]=var_addrl                           #获取用户输入起始地址
            fc_modbus.temp=tag0
            if len(tag0[0]) != 0:
                fc_modbus.port=tag0[0]   
            else:
                fc_modbus.port='COM1'
            if len(tag0[1]) != 0:
                fc_modbus.addrh0=int(tag0[1])  
            else:
                fc_modbus.addrh0=10
            if len(tag0[2]) != 0:
                fc_modbus.code0=int(tag0[2])  
            else:
                fc_modbus.code0=3
            if len(tag0[3]) != 0:
                fc_modbus.rwaddr0=int(tag0[3])   
            else:
                fc_modbus.rwaddr0=0
            if len(tag0[4]) != 0:
                fc_modbus.num=int(tag0[4])   
            else:
                fc_modbus.num=1
            if len(tag0[5]) != 0:
                fc_modbus.addrh1=int(tag0[5])   
            else:
                fc_modbus.addrh1=1
            w_modbus.load0['maximum']=(fc_modbus.addrh0-fc_modbus.addrh1+1)*3*7 #进度条长度
            fc_modbus.runapp('')
            
        except:
            Err='检查整数型参数输入是否正确！'
            w_modbus.Display.insert('end', Err + "\n")    #打印内容到窗口  
            w_modbus.Display.yview_moveto(1)              #刷新内容
            fc_modbus.FoundEn=1
#mqtt主要功能
class fc_mqtt():
    addr='www.icsteam.cn'
    port=1883
    user=''
    password=''
    topic='sensor3'
    En_connect=0
    En_pb=0
    EN_Err=0
    temp=['',0,'','','']
    temp_display=''
    msg=''
    #获取当前时间方法
    def NowTime():                                        
        global now_time
        temp_nt=time.strftime('%Y-%m-%d %H:%M:%S',time.localtime())         #格式化时间
        now_time=f'[{temp_nt}] '       
    #连接成功回调                                     
    def on_connect(client0, userdata, flags, rc):        
        if rc==0:
            info="连接成功"
            fc_mqtt.En_connect=1                         #连接成功标志
            fc_mqtt.EN_Err=0
            fc_mqtt.NowTime()
            w_mqtt.Display.insert('end',now_time+info+'\n')
            w_mqtt.Display.yview_moveto(1)
        else:
            info='连接错误'
            client.disconnect()
            client.loop_stop()
            fc_mqtt.NowTime()
            w_mqtt.Display.insert('end',now_time+info+'>错误码：'+str(rc)+'\n')
            w_mqtt.Display.yview_moveto(1)
            fc_mqtt.EN_Err=1
    #订阅主题
    def con_sub():                                       
        fc_mqtt.En_pb=1
        if fc_mqtt.En_connect==1:
            var_topic=w_mqtt.in_topic.get()
            fc_mqtt.topic=var_topic
            if len(var_topic)!=0:
                client.subscribe(topic=var_topic)
                fc_mqtt.NowTime()
                w_mqtt.Display.insert('end',now_time+'订阅主题：'+var_topic+'\n')
                w_mqtt.Display.yview_moveto(1)
            else:
                w_mqtt.Display.insert('end','订阅的主题不能为空'+'\n')
                w_mqtt.Display.yview_moveto(1)
        else:
            w_mqtt.Display.insert('end','未连连接到代理'+'\n')
            w_mqtt.Display.yview_moveto(1)
    #取消订阅
    def con_unsub():                                     
        if fc_mqtt.En_connect==1:
            var_topic=w_mqtt.in_topic.get()
            fc_mqtt.topic=var_topic
            if len(var_topic)!=0:
                client.unsubscribe(topic=var_topic)
                w_mqtt.Display.insert('end',now_time+'取消订阅：'+var_topic+'\n')
                w_mqtt.Display.yview_moveto(1)
            else:
                w_mqtt.Display.insert('end','取消订阅的主题不能为空'+'\n')
                w_mqtt.Display.yview_moveto(1)
        else:
            w_mqtt.Display.insert('end','未连连接到代理'+'\n')
            w_mqtt.Display.yview_moveto(1)
    def on_message(client, userdata, msg):
        fc_mqtt.NowTime()
        try:
            message=msg.payload.decode('utf-8','replace')
            info=f'主题：{msg.topic}\n{str(message)}'           
        except:
            info='接收的信息非utf-8格式！'
        info=f'{now_time}接收<<<<<<<<<<\n{info}\n'
        w_mqtt.Display.insert('end',info)
        w_mqtt.Display.yview_moveto(1)
    #连接代理
    def connect():                                                          
        global client
        random_id=f'paho_mqtt{random.randint(0000,9999)}'
        client = mqtt.Client(client_id=random_id)
        client.on_connect = fc_mqtt.on_connect                              #连接成功回调
        client.on_message = fc_mqtt.on_message                              #接收到消息
        client.username_pw_set(fc_mqtt.user,fc_mqtt.password)               #账户和密码/必须在connect前使用
        client.connect(fc_mqtt.addr, fc_mqtt.port, 60)                      #服务器地址，端口号，维持心跳60秒
        client.loop_start()                                                 #开启MQTT子线程
    #断开连接
    def disconnect():                                                       
        fc_mqtt.En_connect=0
        client.disconnect()
        client.loop_stop() 
        fc_mqtt.NowTime()    
        w_mqtt.Display.insert('end',now_time+'连接断开\n')
        w_mqtt.Display.yview_moveto(1)
    #发布消息
    def publish():                                                          
        if fc_mqtt.En_connect==1:
            var_topic=w_mqtt.in_topic.get()
            var_msg=w_mqtt.pubmessage.get(1.0,'end')
            var_msg=var_msg[:-1]
            fc_mqtt.msg=var_msg
            fc_mqtt.topic=var_topic
            if len(var_topic)!=0:
                client.publish(topic=var_topic,payload=var_msg)
                fc_mqtt.NowTime()
                info=f'主题：{var_topic}\n{str(var_msg)}'  
                info=f'{now_time}发布>>>>>>>>>>\n{info}\n'
                w_mqtt.Display.insert('end',info)
                w_mqtt.Display.yview_moveto(1)
                fc_mqtt.En_pb=1
            else:
                w_mqtt.Display.insert('end','当前主题为空，无法发布！'+'\n')
                w_mqtt.Display.yview_moveto(1)
        else:
            w_mqtt.Display.insert('end','未连连接到代理！'+'\n')
            w_mqtt.Display.yview_moveto(1)
    #首次运行参数获取
    def run():
        try:
            var_port=w_mqtt.in_port.get()
            var_addr=w_mqtt.in_addr.get()
            var_user=w_mqtt.in_user.get()
            var_password=w_mqtt.in_password.get()
            var_topic=w_mqtt.in_topic.get()
            tag0=['',0,'','','']
            tag0[0]=var_addr                                #获取用户输入的地址
            tag0[1]=var_port                                #获取用户输入的端口号
            tag0[2]=var_user                                #获取用户输入的用户名
            tag0[3]=var_password                            #获取用户输入的密码
            tag0[4]=var_topic                               #获取用户输入的主题
            fc_mqtt.temp=tag0
            if len(tag0[0]) != 0:
                fc_mqtt.addr=tag0[0]
            else:
                fc_mqtt.addr=''
            if len(tag0[1]) != 0:
                fc_mqtt.port=int(tag0[1])  
            else:
                fc_mqtt.port=1883
            if len(tag0[2]) != 0:
                fc_mqtt.user=tag0[2]  
            else:
                fc_mqtt.user=''
            if len(tag0[3]) != 0:
                fc_mqtt.password=tag0[3]   
            else:
                fc_mqtt.password=''
            try:
                fc_mqtt.connect()
            except:
                fc_mqtt.NowTime()
                w_mqtt.Display.insert('end', now_time+'当前地址无效' + "\n")                #打印内容到窗口  
                w_mqtt.Display.yview_moveto(1)
                fc_mqtt.EN_Err=1
        except:
            fc_mqtt.NowTime()
            Err=now_time+'检查输入参数是否正确！'
            w_mqtt.Display.insert('end', Err + "\n")                #打印内容到窗口  
            w_mqtt.Display.yview_moveto(1)                             #刷新内容
            fc_mqtt.EN_Err=1           
#modbus主功能线程>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
def thread0():                              #一个子线程
    thd0=thd.Thread(target=fc_modbus.run)
    thd0.daemon=True                        #主线程结束后，子线程退出
    thd0.start()
    thread1() 
#modbus进度条线程-----------------------------------------------------------
def thread1():
    fc_modbus.FoundEn=0 
    thd0=thd.Thread(target=LoadThread)
    thd0.daemon=True                        #守护进程，主线程结束后，子线程退出
    thd0.start()
def LoadThread():
    while not fc_modbus.FoundEn:
        time.sleep(0.1)
        value0=w_modbus.tag_load.get()
        w_modbus.load0['value']=value0
        w_modbus.update()
        w_modbus.fun.configure(state='disable')  
        app.page_b2.configure(state='disable')  
        if fc_modbus.stop_en :
            w_modbus.fun.configure(state='normal')
            # app.page_b2.configure(state='normal') 
            break    
    else:
        w_modbus.fun.configure(state='normal')
        # app.page_b2.configure(state='normal') 
# modbus停止搜索
def thd_modbus_stop():
    thd2=thd.Thread(target=fc_modbus.stop)
    thd2.daemon=True
    thd2.start()
#mqtt客户端线程>>>>>>>>>>>>>>>>>>>>>>
def thd_mqtt02():
    thd0=thd.Thread(target=fc_mqtt.run)
    thd0.daemon=True                        #主线程结束后，子线程退出
    thd0.start() 
#mqtt运行错误捕捉线程---------------------- 
def thd_mqtt01():                           #连接代理按钮状态判断线程
    thd0=thd.Thread(target=thd_constate)
    thd0.daemon=True                        #主线程结束后，子线程退出
    thd0.start()        
def thd_constate():
    num=0
    while True:
        time.sleep(0.5)
        if fc_mqtt.En_connect:
            Connect.configure(text='取消连接',bootstyle='danger-outline')
            break  
        if fc_mqtt.EN_Err:
            Connect.configure(text='连接代理',bootstyle='success-outline')
        num+=1
        # print(f'计数：{num},连接状态：{fc_mqtt.En_connect}，错误状态：{fc_mqtt.EN_Err}')
        if num==4 :
            break
#窗口--------------------------------------------------------------
#串口通讯>>>>>>>>>>
def switch1():
    global w_modbus
    w_modbus=Ui_box11.AppModbus()
    initEntry_modbus()
    Fbutton=w_modbus.fun
    StpButton=w_modbus.stop
    StpButton.configure(command=thd_modbus_stop)
    Fbutton.configure(command=thread0)
    app.page_b1.configure(bootstyle="primary")
    app.page_b2.configure(bootstyle="info-outline")
    try:
        fc_mqtt.temp_display=w_mqtt.Display.get(1.0,'end')
        com_list0=com_list()
        w_modbus.Display.insert('end', '串口列表：\n'+com_list0)    #打印内容到窗口  
        w_modbus.Display.yview_moveto(1)                            #刷新内容
    except:
        com_list0=com_list()
        w_modbus.Display.insert('end', '串口列表：\n'+com_list0)    #打印内容到窗口  
        w_modbus.Display.yview_moveto(1)                            #刷新内容
        pass
#初始化输入
def initEntry_modbus():                                          
    w_modbus.Display.insert('end',fc_modbus.temp_display)
    w_modbus.Display.delete('%s - 2c' % 'end')                   #删除最后空行
    if fc_modbus.FoundEn==0:
        w_modbus.in_port.insert(0,'COM1')                        #端口 
        w_modbus.in_addrh.insert(0,10)                           #最高地址
        w_modbus.in_code.insert(0,3)                             #功能码
        w_modbus.in_rwaddr.insert(0,0)                           #读取地址
        w_modbus.in_num.insert(0,1)                              #读取数量
        w_modbus.in_addrl.insert(0,1)                              #读取数量
    else:
        w_modbus.in_port.insert(0,fc_modbus.temp[0])
        w_modbus.in_addrh.insert(0,fc_modbus.temp[1])
        w_modbus.in_code.insert(0,fc_modbus.temp[2])
        w_modbus.in_rwaddr.insert(0,fc_modbus.temp[3])
        w_modbus.in_num.insert(0,fc_modbus.temp[4])
        w_modbus.in_addrl.insert(0,fc_modbus.temp[5])


#mqtt客户端>>>>>>>>>>
def conswitch():                            #单按钮连接断开判断
    # Connect=w_mqtt.connect0
    if fc_mqtt.En_connect==0:
        thd_mqtt02()                        #主功能线程
        thd_mqtt01()                        #连接按钮状态判断线程
        Connect.configure(text='取消连接',bootstyle='danger-outline')
    if fc_mqtt.En_connect==1:
        fc_mqtt.disconnect()
        Connect.configure(text='连接代理',bootstyle='success-outline')
def switch2():                              #mqtt界面
    global w_mqtt
    global Connect
    w_mqtt=Ui_box11.AppMQTT()
    initEntry_mqtt()
    app.page_b2.configure(bootstyle="info")
    app.page_b1.configure(bootstyle="primary-outline")
    Connect=w_mqtt.connect0
    Connect.configure(command=conswitch)
    if fc_mqtt.En_connect == 1:
        Connect.configure(text='取消连接',bootstyle='danger-outline')
    EnSub=w_mqtt.Ensub
    EnSub.configure(command=fc_mqtt.con_sub)
    UnEnsub=w_mqtt.UnEnsub
    UnEnsub.configure(command=fc_mqtt.con_unsub)
    Publish=w_mqtt.pubb
    Publish.configure(command=fc_mqtt.publish)
    try:
        fc_modbus.temp_display=w_modbus.Display.get(1.0,'end')
    except:
        pass
def initEntry_mqtt():                                           #初始化输入
    w_mqtt.Display.insert('end',fc_mqtt.temp_display)
    w_mqtt.Display.delete('%s - 2c' % 'end')                    #删除最后空行
    w_mqtt.in_user.insert(0,fc_mqtt.temp[2])
    w_mqtt.in_password.insert(0,fc_mqtt.temp[3])
    w_mqtt.in_topic.insert(0,fc_mqtt.topic)
    w_mqtt.pubmessage.insert('insert',fc_mqtt.msg)
    if fc_mqtt.En_connect==0:
        w_mqtt.in_addr.insert(0,'www.icsteam.cn')            #地址
        w_mqtt.in_port.insert(0,1883)                           #端口                
    else:
        w_mqtt.in_addr.insert(0,fc_mqtt.temp[0])
        w_mqtt.in_port.insert(0,fc_mqtt.temp[1])
def auto_select_theme():
    #获取当前小时时间
    def NowTime():
        # global now_time,now_time1
        temp_nt=time.strftime('%H',time.localtime())         #格式化时间
        return temp_nt
    now_Hour=int(NowTime())
    print(now_Hour)
    if now_Hour>=19 or now_Hour<=7:
        app.select_theme_2(2)
        app.theme_select_combo.insert(0,'深色主题')
    else :
        app.select_theme_2(1)
        app.theme_select_combo.insert(0,'浅色主题')

if __name__ =='__main__':
    app=Ui_box11.MainWindows()
    # 自动选择主题
    auto_select_theme()
    switch1()                                       #默认启动窗口
    app.title('串口通讯参数搜索v1.4')
    wh=app.winfo_screenheight()
    ww=app.winfo_screenwidth()
    mwh=440
    mww=530
    x=(ww-mww)/2
    y=(wh-mwh)/2
    app.geometry("%dx%d+%d+%d"%(mww,mwh,x,y))       #窗口居中显示     
    app.resizable(width=0, height=0)                #限制窗口最大化
    # def setIcon():    
    #     tmp = open("tmp.ico","wb+")  
    #     tmp.write(base64.b64decode(img))            #写入到临时文件中
    #     tmp.close()
    #     app.iconbitmap("tmp.ico")                   #设置图标
    #     os.remove("tmp.ico") 
    # setIcon()
    app.page_b1.configure(command=switch1)
    app.page_b2.configure(command=switch2)
    app.page_b2.configure(state='disable') 
    app.page_about.configure(command=app.showinfo)
    
    # app.page_b2.configure()  
    app.mainloop()